package cz.najman.emdroid.connection;

import java.util.HashMap;
import java.util.Map;

import javax.xml.parsers.ParserConfigurationException;

import org.xml.sax.SAXException;

import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.net.Uri;
import android.util.Log;
import cz.najman.emdroid.content.ContentHandler;
import cz.najman.emdroid.content.ContentProducer;
import cz.najman.emdroid.exception.RemoteOperationNotSuccessfull;
import cz.najman.emdroid.exception.UnknownMethodException;
import cz.najman.emdroid.listener.OnResponseDataParsedListener;
import cz.najman.emdroid.provider.EMDroidDataProvider;
import cz.najman.emdroid.provider.Flags;
import cz.najman.emdroid.utils.LocalToRemoteUriSwitcher;
import cz.najman.emdroid.utils.RequestIdentifier;
import cz.najman.emdroid.utils.UriSwitcher;

/**
 * @author  Lukáš Najman
 */
public class SimpleProccesor implements Proccesor {

	// TAG constant used for logging
	private static final String TAG = "SimpleProcessor";
	
	// Resolver for doing the database work
	private final ContentResolver mResolver;	
	// Uri switcher responsible for switching from local applications uris to remote
	// uris used in the remote calls
	/**
	 */
	private UriSwitcher mUriSwitcher;
	// Connection method for executing HTTP requests
	/**
	 */
	private final ConnectionMethod mConnection;
	// Listener that should be registered at ResponseHandler	
	/**
	 */
	private final OnResponseDataParsedListener mListener;
	// Map that keeps the records about currently executing requests
	private final Map<Long, RequestIdentifier> mRequests;	
	
	/**
	 * Creates new RemoteMethodExecutor, capable of database operations
	 * 
	 * @param resolver ContentResolver
	 * @param context Application context
	 */
	public SimpleProccesor(ContentResolver resolver, Context context) {		
		mResolver = resolver;		
		mRequests = new HashMap<Long, RequestIdentifier>();		
		mUriSwitcher = new LocalToRemoteUriSwitcher(EMDroidDataProvider.mUriMatcher, context);		
		mConnection = new RestConnectionMethod();		
		mListener = new OnDataParsedListener();				
	}
	
	@Override
	public int create(RequestIdentifier requestIdentifier, ContentHandler contentHandler, ContentProducer contentProducer) {
		
		// Get the data from content producer, so we can insert them to the database
		final ContentValues values = contentProducer.getData();
		// Get the localUri, needed for database insert
		final Uri localUri = requestIdentifier.getUri();
		final String[] parameterTypes = requestIdentifier.getParameterTypes();
		final String[] parameterValues = requestIdentifier.getParameterValues();
		
		//Get the uri of the remote resource		
		final Uri remoteUri = mUriSwitcher.switchUri(localUri, parameterTypes, parameterValues);		
		
		if(remoteUri != null){
			// Set the STATE column of the local resource, so other
			// request can check, if there is not already request pending
			// on the same data, or if something goes wrong with connection,
			// we can use this flag, to retry the operation.
			values.put(Flags.STATE_FLAG, Flags.STATE_POSTING);			
			
			// Actually insert data to the database
			mResolver.insert(localUri, values);		
			
			// Prepare the ResponseHandler
			ResponseHandler responseHandler = null;
			try {
				responseHandler = new ParsableResponseHandler(contentHandler);
			} catch (SAXException e1) {				
				e1.printStackTrace();
			} catch (ParserConfigurationException e1) {				
				e1.printStackTrace();
			}
			responseHandler.registerListener(mListener);
			responseHandler.setRequestId(requestIdentifier.getId());
			
			// Register the request in the request "queue", so we can identify
			// that specific request in ResponseDataListener when fired back
			reqisterRequest(requestIdentifier);
			Log.d(TAG, ">>> Posting to: " + remoteUri);
			
			// Execute and process the request
			int responseCode = ConnectionMethod.RESPONSE_CODE_NONE;
			try {
				responseCode = mConnection.create(remoteUri, responseHandler, contentProducer);
			} catch (Exception e) {
				throw new RemoteOperationNotSuccessfull(e.getCause());
			} finally {
				// Unregister the request as we are done with it
				unregisterRequest(requestIdentifier);				
			}			
			return responseCode;
			
		} else {
			return ConnectionMethod.RESPONSE_CODE_NOT_SUCCESSFULL;
		}			
		
	}

	@Override
	public int retrieve(RequestIdentifier requestIdentifier, ContentHandler contentHandler) {
		// Get the localUri so we can match it against the remoteUris		
		final Uri localUri = requestIdentifier.getUri();
		final String[] parameterTypes = requestIdentifier.getParameterTypes();
		final String[] parameterValues = requestIdentifier.getParameterValues();
		
		//Get the uri of the remote resource
		final Uri remoteUri = mUriSwitcher.switchUri(localUri, parameterTypes, parameterValues);
		if(remoteUri != null){
			// Prepare the ResponseHandler 
			ResponseHandler responseHandler = null;
			try {
				responseHandler = new ParsableResponseHandler(contentHandler);
			} catch (SAXException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} catch (ParserConfigurationException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			responseHandler.registerListener(mListener);		
			responseHandler.setRequestId(requestIdentifier.getId());
			
			// Register the request in the request "queue", so we can identify
			// the concrete request in ResponseDataListener when fired back
			reqisterRequest(requestIdentifier);		
			
			Log.d(TAG, ">>> Retrieving from: " + remoteUri);
			// Execute and process the request
			int responseCode = ConnectionMethod.RESPONSE_CODE_NONE;
			try {
				responseCode = mConnection.retrieve(remoteUri, responseHandler);
			} catch (Exception e) {				
				throw new RemoteOperationNotSuccessfull(e.getCause());
			} finally {
				// Unregister the request as we are done with it
				unregisterRequest(requestIdentifier);	
			}		
					
			return responseCode;
			
		} else {
			return ConnectionMethod.RESPONSE_CODE_NOT_SUCCESSFULL;
		}		
	}

	@Override
	public int update(RequestIdentifier requestIdentifier, ContentHandler contentHandler, ContentProducer contentProducer) {
		
		final ContentValues values = contentProducer.getData();
		final Uri localUri = requestIdentifier.getUri();		
		final String[] parameterTypes = requestIdentifier.getParameterTypes();
		final String[] parameterValues = requestIdentifier.getParameterValues();
		
		values.put(Flags.STATE_FLAG, Flags.STATE_UPDATING);			
		mResolver.update(localUri, values, null, null);		
		
		//Get the uri of the remote resource
		final Uri remoteUri = mUriSwitcher.switchUri(localUri, parameterTypes, parameterValues);
		ResponseHandler responseHandler = null;
		try {
			responseHandler = new ParsableResponseHandler(contentHandler);
		} catch (SAXException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (ParserConfigurationException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		responseHandler.registerListener(mListener);
		responseHandler.setRequestId(requestIdentifier.getId());
		
		reqisterRequest(requestIdentifier);	
				
		try {
			mConnection.update(remoteUri, responseHandler, contentProducer);
		} catch (Exception e) {				
			throw new RemoteOperationNotSuccessfull(e.getCause());
		} finally {
			// Unregister the request as we are done with it
			unregisterRequest(requestIdentifier);	
		}
		
		return responseHandler.getResponseCode();
	}

	@Override
	public int delete(RequestIdentifier requestIdentifier) {
		final ContentValues values = new ContentValues();
		final Uri localUri = requestIdentifier.getUri();
		final String[] parameterTypes = requestIdentifier.getParameterTypes();
		final String[] parameterValues = requestIdentifier.getParameterValues();
		
		values.put(Flags.STATE_FLAG, Flags.STATE_DELETING);	
		mResolver.update(localUri, values, null, null);
		
		//Get the uri of the remote resource
		final Uri remoteUri = mUriSwitcher.switchUri(localUri, parameterTypes, parameterValues);
		
		ResponseHandler responseHandler = null;
		try {
			responseHandler = new ParsableResponseHandler();
		} catch (SAXException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (ParserConfigurationException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		responseHandler.registerListener(mListener);
		responseHandler.setRequestId(requestIdentifier.getId());
		
		reqisterRequest(requestIdentifier);
		try {
			mConnection.delete(remoteUri, responseHandler);
		} catch (Exception e) {				
			throw new RemoteOperationNotSuccessfull(e.getCause());
		} finally {
			// Unregister the request as we are done with it
			unregisterRequest(requestIdentifier);	
		}
			
		
		unregisterRequest(requestIdentifier);
		
		return 0;
	}	
	
	private RequestIdentifier getRequestIdentifier(long requestId){
		return mRequests.get(requestId);
	}
	
	private void unregisterRequest(RequestIdentifier identifier){
		final long id = identifier.getId();
		mRequests.remove(id);
	}
	
	private void reqisterRequest(RequestIdentifier identifier){
		final long requestId = identifier.getId();
		
		mRequests.put(requestId, identifier);		
		
	}	
	
	
	public class OnDataParsedListener implements OnResponseDataParsedListener{
		
		@Override
		public void onParsedDataReceive(ContentValues values, long requestId) {
			
			final RequestIdentifier request = getRequestIdentifier(requestId);
			final Uri uri = request.getUri();
			final int method = request.getMethod();
			values.put(Flags.STATE_FLAG, Flags.STATE_NONE);	
			
			switch (method) {
			case ConnectionMethod.METHOD_GET:
				mResolver.insert(uri, values);
				
				break;
			case ConnectionMethod.METHOD_POST:
				mResolver.insert(uri, values);
				
				break;
			case ConnectionMethod.METHOD_UPDATE:
				mResolver.insert(uri, values);
				
				break;	
			case ConnectionMethod.METHOD_DELETE:
				mResolver.delete(uri, null, null);
				
				break;
				
			default:
				throw new UnknownMethodException("Uknown method " + method);				
			}						
			
		}

		@Override
		public void send(ContentValues values, long requestId) {
			onParsedDataReceive(values, requestId);			
		}
		
	}
}
